﻿#!/usr/bin/env python
# encoding: utf-8
"""
application.py

Created by Xiaoping Tang on 2010-08-25.
Copyright (c) 2010 __MyCompanyName__. All rights reserved.
"""

import urlparse, hashlib, logging, base64, zlib, re, time, struct
import Cookie, StringIO

from urllib import quote, unquote_plus

from google.appengine.ext import webapp
from google.appengine.api import urlfetch
from google.appengine.api.urlfetch import fetch, GET, POST, DELETE
from google.appengine.runtime import apiproxy_errors

H2H_Headers = ["connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te","trailers", "transfer-encoding", "upgrade","x-google-cache-control"]

class iRoxHandler(webapp.RequestHandler):
    
    
    def parseRequest(self, method, *args):
        payload = self.request.body
        try:
            fetch_url = self.request.headers["Zcreal-Nsurl"]
        except Exception, e:
            try:
                fetch_url = base64.b64decode(base64.b64decode(self.request.get("url", "")))
                if "twitter" in fetch_url:
                    self.request.headers.update({'content-type':'application/x-www-form-urlencoded'}) 
            except Exception, e:
                fetch_url = self.request.get("url", "")

        if urlparse.urlparse(fetch_url)[0] not in ('http','https'):
            return

        fetch_method = getattr(urlfetch, method)    
        headers = {}
        # logging.info("%s %s", self.request.headers, self.request.body)
        # logging.info("%s", fetch_url)
        
        for item in self.request.headers.keys():
            # TODO Support http://code.google.com/intl/zh-CN/appengine/docs/python/urlfetch/overview.html
            if item.lower() in ["content-length", "host",  "vary", "via", "x-forwarded-for", "referer"]:
                continue
            if "twitter." in fetch_url and item.lower() in ["content-type"]:
                headers['content-type'] = 'application/x-www-form-urlencoded'
                continue
            headers[item] = self.request.headers[item]
        headers["connection"] = "close"
        return {'fetch_url': fetch_url, 'payload': payload, 'headers': headers, 'fetch_method': fetch_method}
    
    def responseToData(self, response):
        headers = {}
        content = response.content

        for item in response.headers.keys():
            if item.lower() in H2H_Headers:
                continue
            ## if item.lower() == "cache-control":
            ##  self.response.headers["cache-control"] = "no-cache"
            ##  continue
            # TODO Multi-Cookie Support
            if item.lower() == "set-cookie":
                scs = response.headers[item].split(",")
                nsc = ""
                for sc in scs:
                    if nsc == "":
                        nsc = sc
                    elif re.match(r"[ \t]*[0-9]", sc):
                        # expires 2nd part
                        nsc += "," + sc
                    else:
                        headers[item] = nsc.strip()
                        nsc = sc
                headers[item] = nsc.strip()
                continue
            headers[item] = response.headers[item]
        
        return {'headers': headers, 'content': content}

    def fetch_single(self, request):
        headers = request['headers']
        payload = request['payload']
        fetch_url = request['fetch_url']
        fetch_method = request['fetch_method']

        response = urlfetch.fetch(fetch_url, payload, fetch_method, headers, True)
        # logging.info("header %s, body %s", response.headers, response.content)
        return response

    def fetch_big(self, request, size, last):
        range_from = last + 1
        range_to = range_from + 1000*1024
        if range_from >= size:
            return
        if range_to >= size:
            range_to = size - 1
        
        headers = request['headers']
        headers['Range'] = "bytes=%s-%s" % (range_from, range_to)
        logging.info('Fetch_BIG: %s' % (headers['Range']) )
        return self.fetch_single(request)

    def proxy(self, method, *args):
        
        request = self.parseRequest(method, *args)
        if request is None:
            return

        try:
            response = self.fetch_single(request)
            response_data = self.responseToData(response)
            
            if response.content_was_truncated:
                request_range = self.getValueFromHeaders(request['headers'], 'Range')
                if request_range is not None:
                    m = re.search(r'(\d+)?-(\d+)?', request_range)
                    values = [u and int(u) for u in m.groups()]
                    content_length = values[1] - values[0]
                else:
                    content_length = int(self.getValueFromHeaders(response.headers, 'Content-Length'))
                content = response.content
                while(True):
                    response = self.fetch_big(request, content_length, len(content) - 1)
                    if response is not None:
                        # check Response Content-Range to verify the remote server support Range request.
                        if self.getValueFromHeaders(response.headers, 'Content-Range') is not None:
                            content += response.content
                            continue
                        else:
                            logging.info('Server does not support Range.')
                    # finish fetch all data.
                    response_data['content'] = content
                    break
            
            self.doOutput(response_data)
                
        except Exception, e:
            logging.info("Error:  %s" % e)
            self.error(408)
    
    def get(self, *args):
        self.response.clear()
        self.proxy('GET', *args)
    
    def post(self, *args):
        self.response.clear()
        self.proxy('POST', *args)
        
    def head(self, *args):
        self.response.clear()
        self.proxy('HEAD', *args)
    
    def delete(self, *args):
        self.response.clear()
        self.proxy('DELETE', *args)

    def doOutput(self, data):
        headers = data["headers"]
        if headers is not None:
            for item in headers.keys():
                self.response.headers[item] = headers[item]

        content = data["content"]
        if content is not None:
            self.response.out.write(content)

        return

    def getValueFromHeaders(self, headers, key):
        if headers.has_key(key):
            return headers[key]
        elif headers.has_key(key.lower()):
            return headers[key.lower()]
        else:
            return